﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using DACU.Properties;
using DACU.Tools;
using DACU.Tools.JavaScript;
using DACU.VkontakteApi.Collections;

namespace DACU.Player
{
	[Serializable, DebuggerDisplay("Count = {Count}"), DebuggerTypeProxy(typeof(CollectionDebugView<>))]
	public class Playlist : IList<Track>, INotifyCollectionChanged, INotifyPropertyChanged
	{
		[Serializable, StructLayout(LayoutKind.Sequential)]
		public struct Enumerator : IEnumerator<Track>
		{
			private Playlist _playlist;
			private int _index;
			private int _version;
			private Track _current;

			internal Enumerator(Playlist playlist)
			{
				_playlist = playlist;
				_index = 0;
				_version = playlist._version;
				_current = default(Track);
			}

			public void Dispose()
			{
			}

			public bool MoveNext()
			{
				Playlist playlist = _playlist;
				if ((_version == playlist._version) && (_index < playlist._size))
				{
					_current = playlist._items[_index];
					_index++;
					return true;
				}
				return MoveNextRare();
			}

			private bool MoveNextRare()
			{
				if (_version != _playlist._version)
					throw new InvalidOperationException(Resources.InvalidOperationException_WrongVersion);
				_index = _playlist._size + 1;
				_current = default(Track);
				return false;
			}

			public Track Current
			{
				get { return _current; }
			}

			object IEnumerator.Current
			{
				get
				{
					if ((_index == 0) || (_index == (_playlist._size + 1)))
						throw new InvalidOperationException(Resources.InvalidOperationException_CantHappen);
					return Current;
				}
			}

			void IEnumerator.Reset()
			{
				if (_version != _playlist._version)
					throw new InvalidOperationException(Resources.InvalidOperationException_WrongVersion);
				_index = 0;
				_current = default(Track);
			}
		}

		public event NotifyCollectionChangedEventHandler CollectionChanged;
		public event PropertyChangedEventHandler PropertyChanged;
		public event TrackChangedEventHandler TrackChanged;

		//private const int DefaultCapacity = 4;
		private static readonly Track[] EmptyArray;
		private Track[] _items;
		private int _size;
		//[NonSerialized]
		//private object _syncRoot;
		private int _version;

		private string _name;
		public string Name
		{
			get { return _name; }
			set
			{
				_name = value;
				ChangeProperty("Name");
			}
		}

		private string _path;
		public string Path
		{
			get { return _path; }
			set
			{
				_path = value;
				ChangeProperty("Path");
			}
		}

		private bool _isShuffle = true;
		public bool IsShuffle
		{
			get { return _isShuffle; }
			set
			{
				_isShuffle = value;
				ConfigTools.ConfigFile.IsShuffle = value;
				ChangeProperty("IsShuffle");
			}
		}

		private bool _isFileLoop;
		public bool IsFileLoop
		{
			get { return _isFileLoop; }
			set
			{
				_isFileLoop = value;
				ConfigTools.ConfigFile.IsFileLoop = value;
				ChangeProperty("IsFileLoop");
			}
		}

		private bool _isListLoop;
		public bool IsListLoop
		{
			get { return _isListLoop; }
			set
			{
				_isListLoop = value;
				ConfigTools.ConfigFile.IsListLoop = value;
				ChangeProperty("IsListLoop");
			}
		}

		private History _history = new History();
		public History History
		{
			get { return _history.AsReadOnly(); }
		}

		public int Count
		{
			get { return _size; }
		}

		bool ICollection<Track>.IsReadOnly
		{
			get { return false; }
		}

		public Track this[int index]
		{
			get
			{
				if (index<0 || index >= _size)
					throw new ArgumentOutOfRangeException();
				return _items[index];
			}
			set
			{
				if (index >= _size)
					throw new ArgumentOutOfRangeException();
				_items[index] = value;
				_version++;
			}
		}

		public int Capacity
		{
			get { return _items.Length; }
			set
			{
				if (value != _items.Length)
				{
					if (value < _size)
						throw new ArgumentOutOfRangeException("value", Resources.ArgumentOutOfRangeException_SmallCapacity);
					if (value > 0)
					{
						var destinationArray = new Track[value];
						if (_size > 0)
							Array.Copy(_items, 0, destinationArray, 0, _size);
						_items = destinationArray;
					}
					else
						_items = EmptyArray;
					ChangeProperty("Capacity");
				}
			}
		}

		public bool IsEmpty
		{
			get { return _size < 1; }
		}

		private Track _currentTrack;
		public Track CurrentTrack
		{
			get { return _currentTrack; }
		}

		static Playlist()
		{
			EmptyArray = new Track[0];
		}

		public Playlist()
		{
			_items = EmptyArray;
		}

		public Playlist(IEnumerable<Track> collection)
		{
			if (collection == null)
				throw new ArgumentNullException("collection");
			var is2 = collection as ICollection<Track>;
			if (is2 != null)
			{
				int count = is2.Count;
				_items = new Track[count];
				is2.CopyTo(_items, 0);
				_size = count;
			}
			else
			{
				_size = 0;
				_items = new Track[4];
				using (IEnumerator<Track> enumerator = collection.GetEnumerator())
					while (enumerator.MoveNext())
						Add(enumerator.Current);
			}
		}

		public Playlist(int capacity)
		{
			if (capacity < 0)
				throw new ArgumentOutOfRangeException("capacity", Resources.ArgumentOutOfRangeException_SmallCapacity);
			_items = new Track[capacity];
		}

		void ChangeCollection(NotifyCollectionChangedEventArgs args)
		{
			if (CollectionChanged != null)
				CollectionChanged(this, args);
		}

		void ChangeProperty(string propertyName)
		{
			if(PropertyChanged!=null)
				PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
		}

		//void ChangeTrack(string newSongUrl)
		//{
		//    if (TrackChanged != null)
		//        TrackChanged(newSongUrl);
		//}

		void ChangeTrack(Track track)
		{
			if (TrackChanged != null)
				TrackChanged(track);
		}

		public IEnumerator GetEnumerator()
		{
			return new Enumerator(this);
		}

		IEnumerator<Track> IEnumerable<Track>.GetEnumerator()
		{
			return new Enumerator(this);
		}

		public void Add(Track item)
		{
			if (_size == _items.Length)
				EnsureCapacity(_size + 1);
			_items[_size++] = item;
			_version++;
			ChangeCollection(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add,item,_size));
		}

		public void AddRange(IEnumerable<Track> collection)
		{
			InsertRange(_size, collection);
		}

		private void EnsureCapacity(int min)
		{
			if (_items.Length < min)
			{
				int num = (_items.Length == 0) ? 4 : (_items.Length * 2);
				if (num < min)
					num = min;
				Capacity = num;
			}
		}

		public ReadOnlyCollection<Track> AsReadOnly()
		{
			return new ReadOnlyCollection<Track>(this);
		}

		public int BinarySearch(Track item)
		{
			return BinarySearch(0, Count, item, null);
		}

		public int BinarySearch(Track item, IComparer<Track> comparer)
		{
			return BinarySearch(0, Count, item, comparer);
		}

		public int BinarySearch(int index, int count, Track item, IComparer<Track> comparer)
		{
			if ((index < 0) || (count < 0))
				throw new ArgumentOutOfRangeException((index < 0) ? "index" : "count");
			if ((_size - index) < count)
				throw new ArgumentException(Resources.ArgumentException_InvalidLength);
			return Array.BinarySearch(_items, index, count, item, comparer);
		}

		public void Clear()
		{
			if (_size > 0)
			{
				Array.Clear(_items, 0, _size);
				_size = 0;
			}
			_version++;
			_history.Clear();
			ChangeCollection(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
		}

		public bool Contains(Track item)
		{
			if (item == null)
			{
				//for (int j = 0; j < _size; j++)
				//    if (_items[j] == null)
				//        return true;
				return false;
			}
			//for (int i = 0; i < _size; i++)
			//    if (_items[i].Equals(item))
			//        return true;
			//return false;
			return BinarySearch(item) != -1;
		}

		public void CopyTo(Track[] array)
		{
			CopyTo(array, 0);
		}

		public void CopyTo(Track[] array, int arrayIndex)
		{
			Array.Copy(_items, 0, array, arrayIndex, _size);
		}

		public void CopyTo(int index, Track[] array, int arrayIndex, int count)
		{
			if ((_size - index) < count)
				throw new ArgumentException(Resources.ArgumentException_InvalidLength);
			Array.Copy(_items, index, array, arrayIndex, count);
		}

		public bool Exists(Predicate<Track> match)
		{
			return (FindIndex(match) != -1);
		}

		public int IndexOf(Track item)
		{
			return Array.IndexOf(_items, item, 0, _size);
		}

		public int IndexOf(Track item, int index)
		{
			if (index > _size)
				throw new ArgumentOutOfRangeException("index",Resources.ArgumentOutOfRangeException_Index);
			return Array.IndexOf(_items, item, index, _size - index);
		}

		public int IndexOf(Track item, int index, int count)
		{
			if (index > _size)
				throw new ArgumentOutOfRangeException("index", Resources.ArgumentOutOfRangeException_Index);
			if ((count < 0) || (index > (_size - count)))
				throw new ArgumentOutOfRangeException("count", Resources.ArgumentOutOfRangeException_Count);
			return Array.IndexOf(_items, item, index, count);
		}

		public void Insert(int index, Track item)
		{
			if (index > _size)
				throw new ArgumentOutOfRangeException("index");
			if (_size == _items.Length)
				EnsureCapacity(_size + 1);
			if (index < _size)
				Array.Copy(_items, index, _items, index + 1, _size - index);
			_items[index] = item;
			_size++;
			_version++;
			ChangeCollection(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
		}

		public void InsertRange(int index, IEnumerable<Track> collection)
		{
			if (collection == null)
				throw new ArgumentNullException("collection");
			if (index > _size)
				throw new ArgumentOutOfRangeException("index", Resources.ArgumentOutOfRangeException_Index);
			var is2 = collection as ICollection<Track>;
			//int startIndex = index;
			if (is2 != null)
			{
				int count = is2.Count;
				if (count > 0)
				{
					EnsureCapacity(_size + count);
					if (index < _size)
						Array.Copy(_items, index, _items, index + count, _size - index);
					if (this == is2)
					{
						Array.Copy(_items, 0, _items, index, index);
						Array.Copy(_items, (index + count), _items, (index * 2), (_size - index));
					}
					else
					{
						var array = new Track[count];
						is2.CopyTo(array, 0);
						array.CopyTo(_items, index);
					}
					_size += count;
				}
			}
			else
				using (IEnumerator<Track> enumerator = collection.GetEnumerator())
					while (enumerator.MoveNext())
						Insert(index++, enumerator.Current);
			_version++;
			ChangeCollection(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
		}

		public Track Find(Predicate<Track> match)
		{
			if (match == null)
				throw new ArgumentNullException("match");
			for (int i = 0; i < _size; i++)
			{
				if (match(_items[i]))
					return _items[i];
			}
			return default(Track);
		}

		public Playlist FindAll(Predicate<Track> match)
		{
			if (match == null)
				throw new ArgumentNullException("match");
			var list = new Playlist();
			for (int i = 0; i < _size; i++)
				if (match(_items[i]))
					list.Add(_items[i]);
			return list;
		}

		public int FindIndex(Predicate<Track> match)
		{
			return FindIndex(0, _size, match);
		}

		public int FindIndex(int startIndex, Predicate<Track> match)
		{
			return FindIndex(startIndex, _size - startIndex, match);
		}

		public int FindIndex(int startIndex, int count, Predicate<Track> match)
		{
			if (startIndex > _size)
				throw new ArgumentOutOfRangeException("startIndex",Resources.ArgumentOutOfRangeException_Index);
			if ((count < 0) || (startIndex > (_size - count)))
				throw new ArgumentOutOfRangeException("count", Resources.ArgumentOutOfRangeException_Count);
			if (match == null)
				throw new ArgumentNullException("match");
			int num = startIndex + count;
			for (int i = startIndex; i < num; i++)
				if (match(_items[i]))
					return i;
			return -1;
		}

		public void ForEach(Action<Track> action)
		{
			if (action == null)
				throw new ArgumentNullException("action");
			for (int i = 0; i < _size; i++)
				action(_items[i]);
		}

		public Playlist GetRange(int index, int count)
		{
			if ((index < 0) || (count < 0))
				throw new ArgumentOutOfRangeException((index < 0) ? "index" : "count");
			if ((_size - index) < count)
				throw new ArgumentException(Resources.ArgumentException_InvalidLength);
			var list = new Playlist(count);
			Array.Copy(_items, index, list._items, 0, count);
			list._size = count;
			return list;
		}

		public bool Remove(Track item)
		{
			int index = IndexOf(item);
			if (index >= 0)
			{
				RemoveAt(index);
				_history.Remove(index);
				return true;
			}
			return false;
		}

		//public int RemoveAll(Predicate<Track> match)
		//{
		//    if (match == null)
		//        throw new ArgumentNullException("match");
		//    int index = 0;
		//    while ((index < _size) && !match(_items[index]))
		//        index++;
		//    if (index >= _size)
		//        return 0;
		//    int num2 = index + 1;
		//    while (num2 < _size)
		//    {
		//        while ((num2 < _size) && match(_items[num2]))
		//            num2++;
		//        if (num2 < _size)
		//            _items[index++] = _items[num2++];
		//    }
		//    Array.Clear(_items, index, _size - index);
		//    int num3 = _size - index;
		//    _size = index;
		//    _version++;
		//    ChangeCollection(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
		//    return num3;
		//}

		public void RemoveAt(int index)
		{
			if (index >= _size)
				throw new ArgumentOutOfRangeException("index");
			_size--;
			Track t = _items[index];
			if (index < _size)
				Array.Copy(_items, index + 1, _items, index, _size - index);
			_items[_size] = default(Track);
			_version++;
			_history.Remove(index);
			ChangeCollection(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t,index));
		}

		public void RemoveRange(int index, int count)
		{
			if ((index < 0) || (count < 0))
				throw new ArgumentOutOfRangeException((index < 0) ? "index" : "count");
			if ((_size - index) < count)
				throw new ArgumentException(Resources.ArgumentException_InvalidLength);
			if (count > 0)
			{
				_size -= count;
				for (int i = 0; i < count; i++)
					_history.Remove(i + index);
				if (index < _size)
					Array.Copy(_items, index + count, _items, index, _size - index);
				Array.Clear(_items, _size, count);
				_version++;
				ChangeCollection(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
			}
		}

		void SaveToFile(string fileName)
		{
			var sb = new StringBuilder();
			sb.AppendFormat("{0}\n",JavaScriptString.QuoteString(Name));
			ForEach(track=>sb.AppendFormat("{0}\n",track.ToJson()));
			File.WriteAllText(fileName,sb.ToString());
		}

		public void Save(string fileName=null)
		{
			if(String.IsNullOrWhiteSpace(fileName)&& String.IsNullOrWhiteSpace(Path))
				throw new ArgumentNullException("fileName");
			if(!String.IsNullOrWhiteSpace(fileName))
			{
				fileName = System.IO.Path.GetFullPath(fileName);
				if (File.Exists(fileName))
				{
					Path = fileName;
					SaveToFile(fileName);
					return;
				}
				else if (Directory.Exists(System.IO.Path.GetDirectoryName(fileName)))
				{
					Path = fileName;
					SaveToFile(fileName);
					return;
				}
				else
					throw new ArgumentException(Resources.ArgumentException_WrongPath, "fileName");
			}
			if(!String.IsNullOrWhiteSpace(Path))
			{
				Path = System.IO.Path.GetFullPath(Path);
				if (File.Exists(Path))
				{
					SaveToFile(Path);
					return;
				}
				else if (Directory.Exists(System.IO.Path.GetDirectoryName(Path)))
				{
					SaveToFile(Path);
					return;
				}
			}
			throw new ArgumentException(Resources.ArgumentException_WrongPath, "fileName");
		}

		void LoadFromFile(string fileName)
		{
			string[] file = File.ReadAllLines(fileName);
			if (file==null || file.Length<1)
				throw new ArgumentException(Resources.ArgumentException_InvalidFileFormat, "fileName");
			var sb = new StringBuilder();
			var name = file.Where(str => !str.StartsWith("{A:\""));
			foreach (string line in name)
				sb.AppendLine(line);
			Name = sb.ToString().Trim();
			Clear();
			AddRange(file.Where(str => str.StartsWith("{A:\"")).Select(Track.FromJson));
		}

		public void Load(string fileName = null)
		{
			if (String.IsNullOrWhiteSpace(fileName) && String.IsNullOrWhiteSpace(Path))
				throw new ArgumentNullException("fileName");
			if (!String.IsNullOrWhiteSpace(fileName))
			{
				if (!File.Exists(fileName))
					throw new ArgumentException(Resources.ArgumentException_FileNotExists.FormatStr(fileName), "fileName");
				Path = fileName;
				LoadFromFile(fileName);
				return;
			}
			if (!String.IsNullOrWhiteSpace(Path))
			{
				if (!File.Exists(Path))
					throw new ArgumentException(Resources.ArgumentException_FileNotExists.FormatStr(fileName), "fileName");
				LoadFromFile(Path);
				return;
			}
		}

		public static Playlist FromVKAudioCollection(VKAudioCollection collection,string playlistName =null)
		{
			if(collection==null || collection.IsEmpty)
				throw new ArgumentNullException("collection");
			var playlist = new Playlist(collection.Select(Track.FromVKAudio))
			               	{
			               		Name = String.IsNullOrWhiteSpace(playlistName) ? "VK collection" : playlistName
			               	};
			return playlist;
		}

		/*Track*/ int Next(int prevId,int next=0)
		{
			int nextId = prevId + 1 - next;
			_history.InHistory(nextId);
			_currentTrack = _items[nextId];
			//_items[nextId].IsCurrentlyPlays = true;
			return /*_items[nextId]*/nextId;
		}

		public /*Track*/ int PlayNext(/*int index= -1*/)
		{
			if (IsFileLoop)
				return !_history.IsEmpty ? /*_items[_history.Last]*/ _history.Last : /*null*/-1;
			if(IsShuffle)
			{
			Label_Shuff:
				var random = new Random();
				int nextTrackId = random.Next(0, _size - 1);
				//if (!_history.IsEmpty)
				//    _items[_history.Last].IsCurrentlyPlays = false;
				if (_items[nextTrackId].Enabled)
				{
					_history.InHistory(nextTrackId);
					_currentTrack = _items[nextTrackId];
					//_items[nextTrackId].IsCurrentlyPlays = true;
					return /*_items[nextTrackId]*/ nextTrackId;
				}
				else goto Label_Shuff;
			}
			{
				int prevId = -1;
				if (!_history.IsEmpty)
				{
					prevId = _history.Last;
					//_items[prevId].IsCurrentlyPlays = false;
				}
				if (prevId == -1)
				{
					return _size > 0 ? Next(prevId) : /*null*/-1;
				}
				else return (prevId + 1) < _size ? Next(prevId) : IsListLoop ? Next(prevId, prevId + 1) : /*null*/-1;
			}
		}

		public /*Track*/ int PlayPrev(/*int index = -1*/)
		{
			if (_history.IsEmpty) return /*null*/-1;
			Label_Prev:
			int currentId = _history.Last;
			int i = _history.OutHistory();
			if (i == -1) return /*null*/-1;
			if (_items[i].Enabled)
			{
				_currentTrack = _items[i];
				//_items[currentId].IsCurrentlyPlays = false;
				//_items[i].IsCurrentlyPlays = true;
				return /*_items[i]*/ i;
			}
			else goto Label_Prev;
		}

		public /*Track*/ void PlayIndex(int index = -1)
		{
			if (index > -1)
			{
				//if (!_history.IsEmpty)
				//    _items[_history.Last].IsCurrentlyPlays = false;
				_history.InHistory(index);
				_currentTrack = _items[index];
				//_items[index].IsCurrentlyPlays = true;
				if (!_items[index].Enabled) _items[index].Enabled = true;
				//ChangeTrack(_items[index].Path);
				ChangeTrack(_items[index]);
			}
		}

		public void PlayTrack(Track track)
		{
			PlayIndex(IndexOf(track));
		}

		public Track GetCurrentPlays()
		{
			for (int i = 0; i < _size; i++)
			{
				if (_items[i].IsCurrentlyPlays)
					return _items[i];
			}
			return null;
		}
	}
}
